package wili.misc;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Stream;

import wili.c.Ca;
import wili.c.Misc9a;

/**
 * 本质：以自然数进位的方式来穷举【一横多纵式】中的路径。
 *
 * @author weila 2020-10-11
 */
@SuppressWarnings("all")
class RowExhausterOnOptsList implements Iterator<Object[]> {//使用Object，因每列的数据类型或不同
    /**
     * 默认列值集合。
     */
    final List<List<Object>> optsList;

    final int[] colInds;

    Object[] nextRow, lastRet;

    int times = 0;

    final Set skipSet;//避免重复

    /**
     * 可用来在生成行后，对行中列值进行计算。
     */
    BiFunction<Object, Object[], Object>[] valueFuns;

    /**
     * 可用来在生成行后，判断该行是否合规。
     */
    List<BiFunction<Object, Object[], Boolean>> rowAcceptors;

    public String toString() {
        return Misc9a.strOfList(optsList, "\n ");
    }

    public RowExhausterOnOptsList(boolean repeatable, Object[]... optsList) {
        this(repeatable, Stream.of(optsList).map(aa -> Arrays.asList(aa)).toList());
    }


    Class<?> elementCls;

    public RowExhausterOnOptsList setElementClass(Class<?> cls) {
        this.elementCls = cls;
        return this;
    }

    /**
     * @param repeatable 行中是否允许存在相同的列值。
     * @param columnList every column is a optionList.
     */
    public RowExhausterOnOptsList(boolean repeatable, List<List<Object>> columnList) {
        if (repeatable) {
            skipSet = null;
        } else {
            skipSet = new HashSet<>();
        }
        this.optsList = columnList;
        this.colInds = new int[columnList.size()];
        Arrays.fill(colInds, 0);
    }

    /**
     * 第i个元素有专门的生成逻辑（易于表达第i个元素和第j个元素在值上的依赖关系）
     */
    public void setGeneratorAt(int eleInd, BiFunction<Object, Object[], Object> fun) {
        if (this.valueFuns == null)
            valueFuns = new BiFunction[optsList.size()];
        this.valueFuns[eleInd] = fun;
    }

    /**
     * 第i个元素有专门的生成逻辑（易于表达第i个元素和第j个元素在值上的依赖关系）
     */
    public void addAcceptor(BiFunction<Object, Object[], Boolean> fun) {
        if (fun == null)
            return;
        if (this.rowAcceptors == null)
            rowAcceptors = new ArrayList<>(optsList.size());
        this.rowAcceptors.add(fun);
    }

    public void reset() {
        Arrays.fill(colInds, 0);
        times = 0;
        this.nextRow = null;
        this.hasNext = true;
        if (skipSet != null)
            skipSet.clear();
    }

    @Override
    public Object[] next() {
        if (!hasNext)
            return null;
        if (nextRow == null) {
            if (!this.hasNext())
                return null;
        }
        if (++times == 23)
            Ca.pause();
        lastRet = nextRow;
        this.nextRow = null;
        return lastRet;
    }

    private boolean hasNext = true;

    @Override
    public boolean hasNext() {
        ++inc;
        if (hasNext00()) {
            if (valueFuns != null) {
                //应用值函数
                for (int j = 0; j < nextRow.length; j++) {
                    if (valueFuns[j] != null) {
                        nextRow[j] = valueFuns[j].apply(j, nextRow);
                    }
                }
            }
            if (rowAcceptors != null) {
                for (BiFunction<Object, Object[], Boolean> acc : this.rowAcceptors) {
                    if (!acc.apply("", nextRow)) {
                        this.next();
                        return this.hasNext();
                    }
                }
            }
            return true;
        }
        return false;
    }

    boolean hasNext00() {
        if (++inc == 2)
            Ca.pause();
        //Ca.log(1, ", inc1=" + Refs.inc);
        if (times == 0) {
            //生成第一个结果
            if (skipSet != null)
                skipSet.clear();
            if (elementCls != null) {
                nextRow = (Object[]) Array.newInstance(elementCls, optsList.size());
            } else {
                nextRow = new Object[optsList.size()];
            }
            f1:
            for (int j = 0; j < optsList.size(); j++) {
                if (valueFuns != null && valueFuns[j] != null)
                    continue;
                List optsJ = optsList.get(j);
                for (Object ele : optsJ) {
                    if (skipSet != null && !skipSet.add(ele))
                        continue;
                    nextRow[j] = ele;
                    continue f1;
                }
                Ca.asert(false, "");
                return hasNext = false;
            }
            return true;
        }
        if (!hasNext)
            return false;
        if (nextRow != null)
            return true;
        nextRow = lastRet;// new Object[optsList.size()];
        for (int i = optsList.size() - 1; i >= 0; i--) {
            List opts = optsList.get(i);

            //Ca.log(1, ", inc=" + Refs.inc);
            if (opts == null)
                continue;//valueGenerators[i] not null.
            if (++colInds[i] >= opts.size()) {
                //向左侧借位
                continue;
            } else {
                //左侧进位后，右侧清零
                nextRow[i] = opts.get(colInds[i]);
                f2:
                for (int j = i + 1; j < optsList.size(); j++) {
                    if (valueFuns != null && valueFuns[j] != null)
                        continue;
                    List optsJ = optsList.get(j);
                    for (int k = 0; k < optsJ.size(); k++) {
                        Object ele = optsJ.get(k);
                        if (skipSet != null) {
                            if (!skipSet.add(ele))
                                continue;
                            skipSet.remove(nextRow[j]);
                        }
                        colInds[j] = 0;
                        nextRow[j] = ele;
                        continue f2;
                    }
                    return hasNext = false;
                }
                return true;
            }
        }
        hasNext = false;
        return false;
    }

    public static void main(String[] args) {
        RowExhausterOnOptsList_test.test1(1);
    }

    static int inc;
}
